package cn.thr.ymcc.service.impl;

import cn.thr.ymcc.constant.Cos;
import cn.thr.ymcc.domain.KillActivity;
import cn.thr.ymcc.domain.KillCourse;
import cn.thr.ymcc.dto.KillDto;
import cn.thr.ymcc.dto.KillOrderTimeoutDto;
import cn.thr.ymcc.dto.PreOrder;
import cn.thr.ymcc.mapper.KillActivityMapper;
import cn.thr.ymcc.mapper.KillCourseMapper;
import cn.thr.ymcc.service.IKillCourseService;
import cn.thr.ymcc.util.AssertUtil;
import cn.thr.ymcc.util.CodeGenerateUtils;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.client.producer.SendStatus;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.redisson.api.RSemaphore;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import java.math.BigDecimal;
import java.util.*;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author thr
 * @since 2023-03-14
 */
@Service
public class KillCourseServiceImpl extends ServiceImpl<KillCourseMapper, KillCourse> implements IKillCourseService {

    @Autowired
    private KillActivityMapper killActivityMapper;

    @Autowired
    private RedisTemplate<String,Object> redisTemplate;

    @Autowired
    private RedissonClient redissonClient;

    @Autowired
    private RocketMQTemplate rocketMQTemplate;

    @Transactional
    @Override
    public boolean save(KillCourse entity) {
        //1.线根据活动id查询活动
        KillActivity killActivity = killActivityMapper.selectById(entity.getActivityId());
        //添加时间
        entity.setStartTime(killActivity.getBeginTime());
        entity.setEndTime(killActivity.getEndTime());
        entity.setTimeStr(killActivity.getTimeStr());

        //2.查询今天秒杀活动场次的数据
        Date now = new Date();
        //查询今天秒杀活动的全部数据
        /**
         * 查询kill_sort最大值
         */
        Integer maxNumber = baseMapper.selectMaxSortNumber();
        //查到最大的加一个
        entity.setKillSort(maxNumber+1);
        return super.save(entity);
    }
    /**
     * 定时发送到redis ，将当天的秒杀课程列表及库存缓存到redis里面
     */
    @Override
    public void publish2Redis() {
        //1.将当天的秒杀课程列表查询出来
        //当天待发布的数据
        Date now = new Date();
        List<KillCourse> killCourses = baseMapper.selectList(new QueryWrapper<KillCourse>()
                .le("start_time", now)
                .ge("end_time", now)
                .eq("publish_status", 0));

        //2.发布到redis （库存预热，课程预热）
        Map<String,KillCourse> allKillCourse =new HashMap<>();
        //绑定一个hash 操作的工具对象
        BoundHashOperations<String, Object, Object> hashOps = redisTemplate.boundHashOps(Cos.Kill.KEY_KILL_COURSE);

        for (KillCourse killCours : killCourses) {
            //库存预热
            redisTemplate.opsForValue().set(Cos.Kill.KILL_COURSE_STORE+killCours.getId(),killCours.getKillCount());

            //防止恶意请求
            killCours.setKillCode(UUID.randomUUID().toString());

            //课程预热
            //redisTemplate.opsForHash().put(Cos.Kill.KEY_KILL_COURSE,killCours.getId().toString(),killCours);
            allKillCourse.put(killCours.getId().toString(), killCours);
            //已经发布到redis 了，这个秒杀课程的状态要修改
            killCours.setPublishStatus(1);
            killCours.setPublishTime(now);
            baseMapper.updateById(killCours);
        }
        hashOps.putAll(allKillCourse);
    }

    /**
     * 用户点击“立即抢购” 执行秒杀业务
     * @param killDto
     * @return
     */
    @Override
    public String kill(KillDto killDto) {
        //1.秒杀验证码是否合法
        AssertUtil.isTrue(StringUtils.hasText(killDto.getKillCode()),"验证码不能为空");
        BoundHashOperations<String, Object, Object> hashOps = redisTemplate.boundHashOps(Cos.Kill.KEY_KILL_COURSE);
        KillCourse killCourse = (KillCourse) hashOps.get(killDto.getKillCourseId());
        AssertUtil.isTrue(killDto.equals(killCourse.getKillCode()),"验证码错误");

        //2.秒杀时间是否合法
        Date now = new Date();
        AssertUtil.isTrue(now.after(killCourse.getStartTime())&& now.before(killCourse.getEndTime()),"活动还没开始！" );

        //3.数量限制
        AssertUtil.isTrue(killDto.getKillCount()<=killCourse.getKillLimit(),"每人限抢"+killCourse.getKillLimit()+"份！");

        //4.用户是否秒杀过
        AssertUtil.isTrue(!redisTemplate.hasKey(Cos.Kill.KEY_KILL_HISTORY+killDto.getLoginId()+"~"+killDto.getKillCourseId()),"你不能重复购买");
        //5.redisson信号量 减库存
        //拿到信号量对象
        RSemaphore semaphore = redissonClient.getSemaphore(Cos.Kill.KILL_COURSE_STORE + killDto.getKillCourseId());
        //使用redisson信号量 预减库存   （自带分布式锁）
        boolean tryAcquire = semaphore.tryAcquire(killDto.getKillCount());
        AssertUtil.isTrue(tryAcquire,"抢购失败");

        //6.抢购成功了：生成一个订单编号，创建一个预创订单对象，缓存到redis中，这就是下单资格凭证
        //订单号
        String orderNo = CodeGenerateUtils.generateOrderSn(killDto.getLoginId());
        //预创订单，要根据预创订单来干什么分析！ 预创订单就是在订单服务中拿下来订单的，所以需要订单和订单明细那些数据
        PreOrder preOrder = new PreOrder()
                .setOrderNo(orderNo)
                .setTotalAmount(killCourse.getKillPrice().multiply(new BigDecimal(killDto.getKillCount())))
                .setTotalCount(killDto.getKillCount())
                .setUserId(killDto.getLoginId())
                .setTitle("秒杀订单")
                .setPrice(killCourse.getKillPrice())
                .setCount(killDto.getKillCount())
                .setCourseId(killCourse.getCourseId())
                .setCourseName(killCourse.getCourseName())
                .setCoursePic(killCourse.getCoursePic())
                .setKillCourseId(killCourse.getId());
        //缓存到redis中，这就是下单资格凭证
        redisTemplate.opsForValue().set(orderNo,preOrder);

        //7.保存一个用户抢购当前课程的记录到redis 用来判断用户不能重复秒杀同一个课程
        redisTemplate.opsForValue().set(Cos.Kill.KEY_KILL_HISTORY+killDto.getLoginId()+"~"+killDto.getKillCourseId(),orderNo);

        /**
         * 8.推送一个延迟消息，处理订单超时
         *  消息推给秒杀服务，去操作redis
         * 1)取消下单资格（通过订单编号删除预创订单）
         * 2）库存还原
         */
        KillOrderTimeoutDto killOrderTimeoutDto = new KillOrderTimeoutDto()
                .setKillCount(killDto.getKillCount())
                .setKillCourseId(killDto.getKillCourseId())
                .setOrderNo(orderNo);

        SendResult sendResult = rocketMQTemplate.syncSend(
                Cos.Kill.TOPIC_KILL_ORDER_TIMEOUT + ":" + Cos.Kill.TAGS_KILL_ORDER_TIMEOUT,
                MessageBuilder.withPayload(JSON.toJSONString(killOrderTimeoutDto)).build(),
                3000,
                3
        );

        AssertUtil.isTrue(sendResult.getSendStatus()== SendStatus.SEND_OK,"抢购失败");
        return orderNo;
    }
    /**
     * 处理秒杀下单超时业务
     * @param killOrderTimeoutDto
     */
    @Override
    public void handleKillOrderTimeout(KillOrderTimeoutDto killOrderTimeoutDto) {
        //1)取消西单资格
        redisTemplate.delete(killOrderTimeoutDto.getOrderNo());
        //2）库存量还原
        redisTemplate.opsForValue().increment(Cos.Kill.KILL_COURSE_STORE+killOrderTimeoutDto.getKillCourseId(),killOrderTimeoutDto.getKillCount());
    }
}
